﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using SevenZip;
//using SeniorCapstoneProject.ServiceReference1;

namespace SeniorCapstoneProject
{
    class FileSpliterJoiner
    {

        #region BeginSplit files: Start worker thread for splitting and compressing of files
        public void BeginSplitFiles(string fileLocation, frmMain active, verifyUser hc, string groupName, int status)
        {
            try //basic error handleing
            {
                if (File.Exists(fileLocation)) //Check to make sure file exists and has permissions to read
                {
                    SplitJoinCompress fsj = new SplitJoinCompress(fileLocation, active, hc, groupName, status); //Pass fileLocation and password to be used in thread
                    Thread sjc = new Thread(new ThreadStart(fsj.SplitFiles));
                    sjc.Name = active.ReplaceString(Path.GetFileName(fileLocation)) + "_Notes_Status";
                    sjc.IsBackground = true;
                    active.threadStore.Add(sjc);
                    sjc.Start(); //Start thread
                }
                else
                {
                    //Post error for user
                    active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***Error*** Either you don't have permissions to access this file, it is in use by another program, or the file no longer exists");
                }
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
            }
        }
        #endregion

        #region BeginJoinFiles: Start worker thread for decompressing and joining of files
        public void BeginJoinFiles(string fileLocation, frmMain active)
        {
            try //Basic error handleing
            {
                if (File.Exists(fileLocation)) //Check to make sure file exists and has permissions to read
                {
                    SplitJoinCompress fsj = new SplitJoinCompress(fileLocation, active, null, null, 0);
                    Thread sjc = new Thread(new ThreadStart(fsj.JoinFiles));
                    sjc.IsBackground = true;
                    sjc.Start(); //Start thread
                }
                else
                {
                    //Post error for user
                    active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***Error*** Either you don't have permissions to access this file, it is in use by another program, or the file no longer exists");
                }
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
            }
        }
        #endregion
    }

    public class SplitJoinCompress
    {
        private string fileLocation; //Store location of file for thread
        private int fileParts = 52428800; //Size to split the files into, Setting lower can lead to instability, Default: 52428800
        private string password; //Stores password for encryption
        frmMain active;
        verifyUser hc;
        string activeDir = Properties.Settings.Default.Temp + "\\hct\\"; //Set main directory to store temp files
        private string groupName;
        private int status;

        public SplitJoinCompress() { }

        public SplitJoinCompress(string fileLocation, frmMain active, verifyUser hc, string groupName, int status)
        {
            this.fileLocation = fileLocation;
            this.active = active;
            this.hc = hc;
            this.groupName = groupName;
            this.status = status;
        }

        #region SplitFiles: Splits files into deteremended size and compresses as each one finishes.
        public void SplitFiles()
        {
            try //Basic error checking
            {
                int threadStatus = active.threadActivity.Count - 1;
                long fileOffset = 0; //Store starting posistion of each read
                string currPartPath; //Store full file path after broken into parts
                long totalFileSize; //Set file size to long to hold upto 9223372036854775807 bytes for large files
                int i = 0; //Used to define what part to save the chunck as
                int totalParts = 0;

                //Specify folder name off of file
                string newPath = activeDir + "\\" + hc.Username + "\\Uploads\\" + Path.GetFileNameWithoutExtension(fileLocation);

                //Get file information to split accuratly
                FileInfo fiSource = new FileInfo(fileLocation);

                totalFileSize = (long)fiSource.Length; //Set total size of file
                double tempParts = totalFileSize / fileParts;
                totalParts = Convert.ToInt32(Math.Ceiling(tempParts));

                //Check to see if exists, if it exists check files and finish split
                if (!Directory.Exists(newPath))
                {
                    this.password = RandomPasswordGenerator(); //Get random password to use on entire file
                    if (this.password == null)
                    {
                        throw new System.ArgumentException("Unable to create password");
                    }
                    Directory.CreateDirectory(newPath); //Create directory
                    ArchiveInformation(password, newPath, fiSource, totalParts);
                    
                }
                else //Check existing files and continue at last file attempted
                {
                    currPartPath = newPath + @"\" + fiSource.Name + ".password"; //set currPartPath to be used for encFile
                    this.password = RetrievePassword(currPartPath); //gets password of last session
                    DirectoryInfo diSource = new DirectoryInfo(Path.GetDirectoryName(currPartPath)); //get directory info to compair last session and start where left off
                    FileInfo[] fiPart = diSource.GetFiles(@"*.part****.7z");
                    if (fiPart.Length > 0)
                    {
                        i = fiPart.Length - 1; //i need to equal the last created file
                    }
                    File.Delete(fiPart[i].FullName); //Delete last file made and start from there
                    fileOffset = i * SplitFileSize(totalFileSize); //Correct offset for difference in files made
                    totalFileSize -= i * SplitFileSize(totalFileSize); //Shrinks totalFileSize to make up for the chunk that has already been read
                    for (int k = 0; k < i; k++)
                    {
                        string tester = active.ReplaceString(Path.GetFileName(fileLocation) + ".part" + k.ToString("0000"));
                        string tester2 = active.ReplaceString(Path.GetFileName(fileLocation));
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), tester2 + "_Compression_Status");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), tester + "_Compression");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), tester + "_Compression");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), tester + "_Compression");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), tester + "_Compression_Status");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), tester2 + "_Compression");
                    }
                }
                active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(fileLocation)) + "_Compression_Status");
                active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(fileLocation)) + "_Compression");

                //Read file in chuncks
                using (BinaryReader b = new BinaryReader(File.Open(fileLocation, FileMode.Open)))
                {
                    while (totalFileSize != 0) //Loop through entire file until everything is read
                    {
                        active.threadActivity[threadStatus].WaitOne(Timeout.Infinite);
                        //if (form.threadShutdown[threadStatus].WaitOne(0))
                        //    break;
                        Byte[] byteSourceFile = new Byte[SplitFileSize(totalFileSize)]; //Create byte array to size of defined sizeRemaining
                        b.BaseStream.Seek(fileOffset, SeekOrigin.Begin); //Set binaryreader to start posistion of offset

                        //Set byte array to area that is needed to be split by the reader
                        byteSourceFile = b.ReadBytes((int)SplitFileSize(totalFileSize));

                        //Create new path for file that makes sure the files stay in order
                        currPartPath = newPath + @"\" + fiSource.Name + ".part" + i.ToString("0000");

                        //Read byte array into memory
                        MemoryStream fsNew = new MemoryStream(byteSourceFile);
                        fileOffset += SplitFileSize(totalFileSize); //Adjust fileOffset for next pass through
                        totalFileSize -= SplitFileSize(totalFileSize); //Shrinks totalFileSize to make up for the chunk that has already been read

                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(currPartPath)) + "_Compression");

                        //Set properties to pass over for compression
                        Compress comFile = new Compress(); //Start new instance of compression properties
                        comFile.Location = currPartPath; //Set current file that was just split
                        comFile.Password = password; //Set password to be used in compression
                        comFile.FileName = Path.GetFileName(currPartPath); //Set just the file name
                        SevenZipCompressor szc = new SevenZipCompressor(); //Start new instance of compression settings
                        szc = comFile.SetProperties(comFile); //Set properties and return configured settings
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(currPartPath)) + "_Compression");
                        comFile.StartCompression(szc, comFile, fsNew); //Compress files in compress.cs

                        //Update progress bar
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(currPartPath)) + "_Compression");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(fileLocation)) + "_Compression_Status");
                        active.Invoke(new UpdateCompressionInvoker(active.UpdateCompression), active.ReplaceString(Path.GetFileName(fileLocation)) + "_Compression");
                        active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "Compressed and encrypted file: " + Path.GetFileName(currPartPath));

                        FileSendRecieve fsr = new FileSendRecieve();
                        fsr.StartTransfer(currPartPath + ".7z", i, active, hc, groupName, status, totalParts);
                        i++; //Iterate i for next file name
                    }
                }
            }
            catch (ThreadAbortException)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "Stopped Transfering " + Path.GetFileName(fileLocation) + " and deleted temporary files.");
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
            }
        }
        #endregion

        #region JoinFiles: Decompresses and joins files already split and compressed.
        public void JoinFiles()
        {
            try //Basic error checking
            {
                DirectoryInfo diSource = new DirectoryInfo(Path.GetDirectoryName(fileLocation)); //Get Directory information to start decompression
                string newFileName = Path.Combine(Properties.Settings.Default.ExtractLocation, Path.GetFileNameWithoutExtension(fileLocation));

                if (File.Exists(newFileName)) //Check if file already exists
                {
                    File.Delete(newFileName); //Remove file if already atempted once
                }
                FileStream outStream = new FileStream(newFileName, FileMode.Append); //Create stream to join files in
                diSource = new DirectoryInfo(Path.GetDirectoryName(fileLocation)); //Update directory info
                foreach (FileInfo fiPart in diSource.GetFiles(@"*.part*")) //Join all files that end in .part*
                {
                    Byte[] bytePart = File.ReadAllBytes(fiPart.FullName); //Read file to byte array
                    outStream.Write(bytePart, 0, bytePart.Length); //Add byte array to already created file
                }
                outStream.Close(); //Close stream
                Directory.Delete(Path.GetDirectoryName(fileLocation), true); //Delete directory with temp files
                
                if(!Properties.FileSettings.Default.FileLocations.Contains(newFileName.ToString()))
                {
                    Properties.FileSettings.Default.FileLocations = Properties.FileSettings.Default.FileLocations + newFileName.ToString() + ";";
                    Properties.FileSettings.Default.Save();
                    active.Invoke(new trvLocalUpdate(active.trvLocalUpdate), newFileName);
                }
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "File " + Path.GetFileNameWithoutExtension(fileLocation) + " is ready for use.");
                active.Invoke(new pgBar_Update(active.pgBar_update), -1, -1, Path.GetFileNameWithoutExtension(fileLocation));
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
            }
        }
        #endregion

        #region DecompressFiles: Decompresses files as they come in to check validity.
        public bool DecompressFiles(string file, string passwordFile, verifyUser hc, frmMain frmm)
        {
            this.hc = hc;
            this.active = frmm;
            try
            {
                Compress comFile = new Compress();
                comFile.Location = file; //Set file location
                comFile.Password = RetrievePassword(passwordFile); //Retrieve password to use to decrypt zips
                comFile.FileName = Path.GetFileName(file);

                if (File.Exists(comFile.Location)) //Check if password file is missing or corrupt
                {
                    comFile.StartDecompression(comFile, Path.GetDirectoryName(comFile.Location));
                    File.Delete(comFile.Location);
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
                return false;
            }
        }
        #endregion

        #region SplitFileSize: Returns the amount to make each file seperation.
        private long SplitFileSize(long sizeRemaining)
        {
            if (sizeRemaining < (long)fileParts) //If left over filesize is larger, needs to return the ammount of bytes left to read
            {
                return sizeRemaining;
            }
            else
            {
                return fileParts;
            }
        }
        #endregion

        #region ArchiveInformation: Creates password file to use for decryption on server/client side
        private void ArchiveInformation(string password, string newPath, FileInfo fiSource, int parts)
        {
            try //Basic error checking
            {
                MemoryStream encryptionPasswordFile = new MemoryStream(); //MemoryStream to hold file so it never touches disk until encrypted
                StreamWriter sw = new StreamWriter(encryptionPasswordFile); //Use StreamWriter to create file in memory
                sw.Write(password); //Write password to writer
                sw.Flush(); //Save changes to streamwritere
                Compress encFile = new Compress(); //New instance of properties to inc the file
                String currPartPath = newPath + @"\" + fiSource.Name + ".password"; //set currPartPath to be used for encFile
                encFile.Location = currPartPath; //Set location
                encFile.Password = hc.Password; //Test password....Will get users password
                encFile.FileName = Path.GetFileName(currPartPath); //Set filename
                SevenZipCompressor ezc = encFile.SetProperties(encFile); //New instance of 7zcompressor
                encFile.StartCompression(ezc, encFile, encryptionPasswordFile); //Create encrypted file and clear memorystream

                FileSendRecieve fsr = new FileSendRecieve();
                fsr.StartTransfer(currPartPath + ".7z", 0, active, hc, groupName, status, parts);
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
            }
        }
        #endregion

        #region RetrievePassword: Method used to retrieve password 
        private string RetrievePassword(string fileLocation)
        {
            try //Basic error checking
            {
                //DirectoryInfo diSource = new DirectoryInfo(Path.GetDirectoryName(fileLocation)); //Get directory info to locate password file
                //FileInfo[] fiPass = diSource.GetFiles(@"*.password.7z"); //Locate password file and get information

                if (File.Exists(fileLocation)) //If it find password file return password
                {
                    Compress encFile = new Compress(); //New instance of compress to set properties
                    encFile.Password = hc.Password; //Set password to access password file
                    encFile.Location = fileLocation; //Set the location of password file
                    encFile.StartDecompression(encFile, Path.GetDirectoryName(fileLocation)); //Decrypt file to extract password
                    string encPath = Path.GetDirectoryName(fileLocation) + @"\" + Path.GetFileNameWithoutExtension(fileLocation); //Set path for decrypted password file
                    StreamReader sr = new StreamReader(encPath); //Create reader to read file
                    string password = sr.ReadLine().ToString(); //Pass the password in the file to string to be sent back and used
                    sr.Close(); //Close streamreader
                    File.Delete(encPath); //Delete decrypted file so password can't be seen
                    return password; //Return password to use for other files
                }
                else
                {
                    return null; //Password file was not found or is currupted so it returns null
                }
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
                return null;
            }
        }
        #endregion

        #region RandomPasswordGenerator: Creates a random 64byte string used for encrypting files
        private string RandomPasswordGenerator()
        {
            try
            {
                Random random = new Random((int)DateTime.Now.Ticks); //Set random based off clock
                StringBuilder builder = new StringBuilder();

                //Create 64byte string for password used
                char ch;
                for (int i = 0; i < 64; i++)
                {
                    ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); //Get random letter for password
                    builder.Append(ch); //Store in string builder
                }

                return builder.ToString(); //Return password
            }
            catch (Exception ex)
            {
                active.Invoke(new UpdateMessageInvoker(active.UpdateMessageBoard), "***ERROR*** " + ex.Message);
                return null;
            }
        }
        #endregion

        #region GetFilePartsSize: return fileparts size to be used by other areas
        public int FileParts()
        {
            return fileParts;
        }
        #endregion
    }
}
